package com.jzo2o.orders.manager.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.market.CouponApi;
import com.jzo2o.api.orders.dto.request.OrderCancelReqDTO;
import com.jzo2o.api.orders.dto.response.OrderResDTO;
import com.jzo2o.api.orders.dto.response.OrderSimpleResDTO;
import com.jzo2o.api.trade.RefundRecordApi;
import com.jzo2o.api.trade.dto.response.ExecutionResultResDTO;
import com.jzo2o.api.trade.enums.RefundStatusEnum;
import com.jzo2o.common.constants.UserType;
import com.jzo2o.common.enums.EnableStatusEnum;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.utils.BeanUtils;
import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.common.utils.JsonUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.orders.base.config.OrderStateMachine;
import com.jzo2o.orders.base.constants.RedisConstants;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderRefundStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusChangeEventEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.domain.OrdersCanceled;
import com.jzo2o.orders.base.model.domain.OrdersRefund;
import com.jzo2o.orders.base.model.dto.OrderSnapshotDTO;
import com.jzo2o.orders.manager.model.dto.OrderCancelDTO;
import com.jzo2o.orders.manager.service.IOrdersCanceledService;
import com.jzo2o.orders.manager.service.IOrdersManagerService;
import com.jzo2o.orders.manager.service.IOrdersRefundService;
import com.jzo2o.redis.helper.CacheHelper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.weaver.ast.Or;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.jzo2o.orders.base.constants.FieldConstants.SORT_BY;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersManagerServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersManagerService {

    @Resource
    private IOrdersCanceledService ordersCanceledService;
    @Resource
    private IOrdersManagerService currentProxy;
    @Resource
    private IOrdersRefundService ordersRefundService;
    @Resource
    private RefundRecordApi refundRecordApi;
    @Autowired
    private OrderStateMachine orderStateMachine;
    @Autowired
    private CacheHelper cacheHelper;
    @Resource
    private CouponApi couponApi;

    @Override
    public List<Orders> batchQuery(List<Long> ids) {
        LambdaQueryWrapper<Orders> queryWrapper = Wrappers.<Orders>lambdaQuery().in(Orders::getId, ids).ge(Orders::getUserId, 0);
        return baseMapper.selectList(queryWrapper);
    }

    @Override
    public Orders queryById(Long id) {
        return baseMapper.selectById(id);
    }

    /**
     * 滚动分页查询
     *
     * @param currentUserId 当前用户id
     * @param ordersStatus  订单状态，0：待支付，100：派单中，200：待服务，300：服务中，400：待评价，500：订单完成，600：已取消，700：已关闭
     * @param sortBy        排序字段
     * @return 订单列表
     */
    /*@Override
    public List<OrderSimpleResDTO> consumerQueryList(Long currentUserId, Integer ordersStatus, Long sortBy) {
        //1.构件查询条件
        LambdaQueryWrapper<Orders> queryWrapper = Wrappers.<Orders>lambdaQuery()
                .eq(ObjectUtils.isNotNull(ordersStatus), Orders::getOrdersStatus, ordersStatus)
                .lt(ObjectUtils.isNotNull(sortBy), Orders::getSortBy, sortBy)
                .eq(Orders::getUserId, currentUserId)
                .eq(Orders::getDisplay, EnableStatusEnum.ENABLE.getStatus());
        Page<Orders> queryPage = new Page<>();
        queryPage.addOrder(OrderItem.desc(SORT_BY)); //SORT_BY实际上就是"sort_by"字段
        //实际查询的数据条数取决于 size 参数，即每页显示的条数。默认情况下，size 为 10，即每页查询 10 条数据。即使禁用了查询总记录数,任然会根据size查询
        queryPage.setSearchCount(false); // 数量不用查询了

        //2.查询订单列表
        Page<Orders> ordersPage = baseMapper.selectPage(queryPage, queryWrapper);
        List<Orders> records = ordersPage.getRecords();
        List<OrderSimpleResDTO> orderSimpleResDTOS = BeanUtil.copyToList(records, OrderSimpleResDTO.class);
        return orderSimpleResDTOS;
    }*/

    @Override
    public List<OrderSimpleResDTO> consumerQueryList(Long currentUserId, Integer ordersStatus, Long sortBy) {
        //1.构建查询条件
        LambdaQueryWrapper<Orders> queryWrapper = Wrappers.<Orders>lambdaQuery()
                .eq(ObjectUtils.isNotNull(ordersStatus), Orders::getOrdersStatus, ordersStatus)
                .lt(ObjectUtils.isNotNull(sortBy), Orders::getSortBy, sortBy)
                .eq(Orders::getUserId, currentUserId)
                .eq(Orders::getDisplay, EnableStatusEnum.ENABLE.getStatus())
                //查询时,只要select id,
                .select(Orders::getId); //默认查询全部
        Page<Orders> queryPage = new Page<>();
        queryPage.addOrder(OrderItem.desc(SORT_BY)); //SORT_BY实际上就是"sort_by"字段
        queryPage.setSearchCount(false); // 数量不用查询了

        //查询订单id列表
        Page<Orders> ordersPage = baseMapper.selectPage(queryPage, queryWrapper);
        List<Orders> records = ordersPage.getRecords();
        if (CollUtils.isEmpty(records)) {
            return Collections.emptyList();
        }

        //3. 从List<Orders>里取出List<订单id>, 再找每个id对应的订单详情
        List<Long> ids = records.stream().map(Orders::getId).collect(Collectors.toList());
        /*//  遍历每个id：先根据订单id查询缓存，如果缓存有就直接使用缓存数据；如果缓存没有就需要查询详情，缓存起来
        for (Long id : ids) {
            //从当前用户的hash里查找id对应的订单详情
            //如果找不到：根据订单id查询数据库，将结果再缓存到hash里 (这里要查询多次数据库,因此要把缓存里没有的id收集起来,一次性去数据库中查询出来)
            //将Orders对象转换成OrderSimpleResDTO
        }*/

        //参数1：缓存的key
        //public static final String ORDERS = "ORDERS:PAGE_QUERY:PAGE_%s"; %s是占位符
        String redisKey = String.format(RedisConstants.RedisKey.ORDERS,currentUserId);
        //参数2：要查询的数据id集合
        //参数3：如果某个id对应的数据没有缓存，需要我们提供好查询数据库的方法，返回查询的结果，cacheHelper会将结果缓存起来
        //参数4：将最终返回List里的类型传进去
        return cacheHelper.batchGet(redisKey, ids, new CacheHelper.BatchDataQueryExecutor<Long, OrderSimpleResDTO>() {
            /**
             * 如果没有缓存，就会调用这个方法查询数据库获取数据
             * @param objectIds 没有缓存的id集合
             * @param clazz 每个id对应的数据对象类型
             * @return Map<id, 数据对象>，cacheHelper得到这个返回值，会将整个map直接存储到Redis的hash里
             */
            @Override
            public Map<Long, OrderSimpleResDTO> execute(List<Long> objectIds, Class<OrderSimpleResDTO> clazz) {
                //查询objectIds对应的订单详情列表
                List<Orders> orders = batchQuery(objectIds);
                //将List转换成Map<id,OrderSimpleResDto对象>
                return orders.stream().collect(Collectors.toMap(Orders::getId,o->BeanUtils.toBean(o,OrderSimpleResDTO.class)));
            }
        },OrderSimpleResDTO.class,RedisConstants.Ttl.ORDERS_PAGE_TTL);
    }

    /**
     * 根据订单id查询
     *
     * @param id 订单id
     * @return 订单详情
     */
    @Override
    public OrderResDTO getDetail(Long id) {
        /*Orders orders = queryById(id);
        //懒加载
        //如果此订单是未支付, 且已超时, 就取消订单
        Boolean canceled = cancelOverTimeNoPayOrder(orders);
        if (canceled) {
            orders = getById(id);
        }
        OrderResDTO orderResDTO = BeanUtil.toBean(orders, OrderResDTO.class);
        return orderResDTO;*/

        //优化
        //找状态机的快照缓存
        String snapshotCache = orderStateMachine.getCurrentSnapshotCache(id.toString());
        OrderSnapshotDTO snapshot = JsonUtils.toBean(snapshotCache, OrderSnapshotDTO.class);
        //懒加载
        //如果此订单是未支付, 且已超时, 就取消订单
        Boolean canceled = cancelOverTimeNoPayOrder(snapshot);
        if (canceled) {
            snapshotCache = orderStateMachine.getCurrentSnapshotCache(id.toString());
            snapshot = JsonUtils.toBean(snapshotCache,OrderSnapshotDTO.class);
        }
        OrderResDTO orderResDTO = BeanUtils.toBean(snapshot, OrderResDTO.class);
        return orderResDTO;
    }

    //优化
    private Boolean cancelOverTimeNoPayOrder(OrderSnapshotDTO orders) {
        if (ObjectUtils.equals(orders.getOrdersStatus(), OrderStatusEnum.NO_PAY.getStatus())
                && orders.getCreateTime().isBefore(LocalDateTime.now().minusMinutes(15))) {
            //系统就取消订单, 新增orders_canceled表
            OrderCancelDTO dto = new OrderCancelDTO();
            dto.setId(orders.getId());
            dto.setCurrentUserType(UserType.SYSTEM);
            dto.setCancelReason("超时未支付自动取消");
            currentProxy.cancelNoPayOrder(dto);
            return true;
        }
        return false;
    }
    /*private Boolean cancelOverTimeNoPayOrder(Orders orders) {
        if (ObjectUtils.equals(orders.getOrdersStatus(),OrderStatusEnum.NO_PAY.getStatus())
        && orders.getCreateTime().isBefore(LocalDateTime.now().minusMinutes(15))) {
            //系统就取消订单, 新增orders_canceled表
            OrderCancelDTO dto = new OrderCancelDTO();
            dto.setId(orders.getId());
            dto.setCurrentUserType(UserType.SYSTEM);
            dto.setCancelReason("超时未支付自动取消");
            currentProxy.cancelNoPayOrder(dto);
            return true;
        }
        return false;
    }*/

    /**
     * 订单评价
     *
     * @param ordersId 订单id
     */
    @Override
    @Transactional
    public void evaluationOrder(Long ordersId) {
//        //查询订单详情
//        Orders orders = queryById(ordersId);
//
//        //构建订单快照
//        OrderSnapshotDTO orderSnapshotDTO = OrderSnapshotDTO.builder()
//                .evaluationTime(LocalDateTime.now())
//                .build();
//
//        //订单状态变更
//        orderStateMachine.changeStatus(orders.getUserId(), orders.getId().toString(), OrderStatusChangeEventEnum.EVALUATE, orderSnapshotDTO);
    }

    /**
     * 查询超时未支付的订单
     *
     * @param count
     */
    @Override
    public List<Orders> queryOverTimeNoPayOrdersByCount(int count) {
        List<Orders> list = lambdaQuery()
                //查询待支付状态的订单
                .eq(Orders::getPayStatus, OrderPayStatusEnum.NO_PAY.getStatus())
                //查询15分钟之前下的订单，即下单时间早于 15分钟之前的时间
                .lt(Orders::getCreateTime, LocalDateTime.now().minusMinutes(15))
                .last("limit " + count)
                .list();
        return list;
    }

    /**
     * 取消超时未支付订单
     *
     * @param dto
     */
    @Override
    @Transactional
    public void cancelNoPayOrder(OrderCancelDTO dto) {
        //1. 修改订单的状态：update orders set order_status = 已取消 where id = 订单id
        /*lambdaUpdate()
                .eq(Orders::getId,dto.getId())
                .set(Orders::getOrdersStatus, OrderStatusEnum.CANCELED.getStatus())
                .update();*/
        //用状态机来更改订单状态
        OrderSnapshotDTO snapshot = new OrderSnapshotDTO();
        snapshot.setCancellerId(dto.getCurrentUserId());
        snapshot.setCancelerName(dto.getCurrentUserName());
        snapshot.setCancellerType(dto.getCurrentUserType());
        snapshot.setCancelReason(dto.getCancelReason());
        //设置dto.getUserId()分片键 来进行分库操作
        orderStateMachine.changeStatus(dto.getUserId(), dto.getId().toString(), OrderStatusChangeEventEnum.CANCEL, snapshot);
        //2. 新增一条订单取消记录
        OrdersCanceled ordersCanceled = new OrdersCanceled();
        ordersCanceled.setId(dto.getId());
        ordersCanceled.setCancellerId(dto.getCurrentUserId());
        ordersCanceled.setCancelerName(dto.getCurrentUserName());
        ordersCanceled.setCancellerType(dto.getCurrentUserType());
        ordersCanceled.setCancelReason(dto.getCancelReason());
        ordersCanceled.setCancelTime(LocalDateTime.now());
        ordersCanceledService.save(ordersCanceled);

    }

    @Override
    public void cancel(OrderCancelReqDTO dto) {
        //查询订单: 如果订单不在就抛异常
        Orders order = getById(dto.getId());
        if (order == null) {
            throw new BadRequestException("订单不存在");
        }

        //准备数据
        OrderCancelDTO orderCancelDTO = new OrderCancelDTO();
        orderCancelDTO.setId(order.getId());
        orderCancelDTO.setCurrentUserType(UserContext.currentUser().getUserType());
        orderCancelDTO.setCurrentUserName(UserContext.currentUser().getName());
        orderCancelDTO.setCurrentUserId(UserContext.currentUserId());
        orderCancelDTO.setCancelReason(dto.getCancelReason());

        BigDecimal discountAmount = order.getDiscountAmount();
        //判断订单的状态:
        if (ObjectUtils.equals(order.getOrdersStatus(), OrderStatusEnum.NO_PAY.getStatus())) {
            //如果order_status是未支付状态, 就取消未支付的订单
            currentProxy.cancelNoPayOrder(orderCancelDTO);
            //退回优惠券
            if (ObjectUtils.isNotNull(discountAmount) && discountAmount.compareTo(new BigDecimal(0)) > 0) {
                //退回优惠券
                Long orderId = order.getId();
                couponApi.useBack(orderId);
            }
        } else if (ObjectUtils.equals(order.getOrdersStatus(), OrderStatusEnum.DISPATCHING.getStatus())) {
            //如果order_status是派单中状态, 就取消已支付的订单
            //如果discount_amount优惠金额不为空,且>0,则使用了优惠券,需要退回优惠券

            if (ObjectUtils.isNotNull(discountAmount) && discountAmount.compareTo(new BigDecimal(0)) > 0) {
                //退回优惠券
                Long orderId = order.getId();
                new Thread(() -> couponApi.useBack(orderId));
            }
                //discount_amount优惠金额为空
                //准备 交易单id 和 退款金额
                orderCancelDTO.setTradingOrderNo(order.getTradingOrderNo());
                orderCancelDTO.setRealPayAmount(order.getRealPayAmount());
                //要取消派单中已支付的订单：修改订单状态、新增了取消记录、新增了待退款记录
                currentProxy.cancelDispatchingOrder(orderCancelDTO);
                //申请退款：为防止取消派单中订单执行流程过长、耗时太久，用户体验不好，我们申请退款使用异步（采用线程方式）
                new Thread(() -> {
                    requestRefundOrder(order.getId(), order.getRealPayAmount(), order.getTradingOrderNo());
                }).start();

        } else {
            throw new ForbiddenOperationException("不支持取消订单");
        }

    }

    /**
     * 退款
     *
     * @param orderId
     * @param realPayAmount
     * @param tradingOrderNo
     */
    @Override
    public void requestRefundOrder(Long orderId, BigDecimal realPayAmount, Long tradingOrderNo) {
        //1.通过trade服务发起退款申请, 得到退款操作的结果
        ExecutionResultResDTO res = refundRecordApi.refundTrading(tradingOrderNo, realPayAmount);
        //2.处理退款的结果
        currentProxy.refundOrder(res, orderId);
    }

    @Override
    @Transactional
    public void refundOrder(ExecutionResultResDTO res, Long orderId) {
        //1. 获取退款状态,这个状态是trade服务里状态值
        Integer refundStatus = res.getRefundStatus();

        //2. 判断退款的状态, 如果是退款中状态, 就直接结束
        if (ObjectUtils.equals(refundStatus, RefundStatusEnum.SENDING.getCode())) {
            return;
        }
        //3. 如果不是退款中, 就需要完成退款后的处理
        //3.1 将trade服务返回的结果状态，转换成Orders表里自己枚举对应的状态
        if (ObjectUtils.equals(refundStatus, RefundStatusEnum.SUCCESS.getCode())) {
            refundStatus = OrderRefundStatusEnum.REFUND_SUCCESS.getStatus();
        } else if (ObjectUtils.equals(refundStatus, RefundStatusEnum.FAIL.getCode())) {
            refundStatus = OrderRefundStatusEnum.REFUND_FAIL.getStatus();
        }
        //3.2 修改orders表的状态
        /*boolean updated = lambdaUpdate()
                .eq(Orders::getId, orderId)
                .set(Orders::getRefundStatus, res.getRefundStatus())
                .set(Orders::getRefundNo, res.getRefundNo())
                .set(Orders::getRefundId, res.getRefundId())
                .update();
        if (updated) {
            //3.3 删除待退款记录
            ordersRefundService.removeById(orderId);
        }*/

        //退款成功后需要保存订单状态机的快照
        //根据订单号查询当前快照信息, 得到json字符串
        String snapshotJson = orderStateMachine.getCurrentSnapshot(orderId.toString());
        OrderSnapshotDTO snapshot = JsonUtils.toBean(snapshotJson, OrderSnapshotDTO.class);
        snapshot.setThirdRefundOrderId(res.getRefundId());
        snapshot.setRefundNo(res.getRefundNo());
        snapshot.setRefundStatus(res.getRefundStatus());
        //获取分片键UserId
        Orders orders = lambdaQuery().eq(Orders::getId, orderId).one();
        orderStateMachine.saveSnapshot(orders.getUserId(), orderId.toString(), OrderStatusEnum.CLOSED, snapshot);

        //3.3 删除待退款记录
        ordersRefundService.removeById(orderId);
    }

    /**
     * 取消派单中已支付的订单
     *
     * @param dto
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelDispatchingOrder(OrderCancelDTO dto) {
        //1.修改订单orders表数据 把order_status修改为已关闭，把refund_status修改为退款中
        /*boolean updated = lambdaUpdate()
                .set(Orders::getOrdersStatus, OrderStatusEnum.CLOSED.getStatus())
                .set(Orders::getRefundStatus, OrderRefundStatusEnum.REFUNDING.getStatus())
                .eq(Orders::getId, dto.getId())
                .update();
        if (!updated) {
            throw new CommonException("订单不存在");
        }*/
        //用状态机来修改订单状态
        OrderSnapshotDTO snapshot = new OrderSnapshotDTO();
        snapshot.setCancellerId(dto.getCurrentUserId());
        snapshot.setCancelerName(dto.getCurrentUserName());
        snapshot.setCancellerType(dto.getCurrentUserType());
        snapshot.setCancelReason(dto.getCancelReason());
        orderStateMachine.changeStatus(dto.getUserId(), dto.getId().toString(), OrderStatusChangeEventEnum.CLOSE_DISPATCHING_ORDER, snapshot);
        //2.新增一条取消订单记录orders_canceled表
        OrdersCanceled ordersCanceled = new OrdersCanceled();
        ordersCanceled.setId(dto.getId());
        ordersCanceled.setCancellerId(dto.getCurrentUserId());
        ordersCanceled.setCancelerName(dto.getCurrentUserName());
        ordersCanceled.setCancellerType(dto.getCurrentUserType());
        ordersCanceled.setCancelReason(dto.getCancelReason());
        ordersCanceled.setCancelTime(LocalDateTime.now());
        ordersCanceledService.save(ordersCanceled);
        //3.新增一条待退款记录orders_refund表
        OrdersRefund ordersRefund = new OrdersRefund();
        ordersRefund.setId(dto.getId());
        ordersRefund.setTradingOrderNo(dto.getTradingOrderNo());
        ordersRefund.setRealPayAmount(dto.getRealPayAmount());
        ordersRefundService.save(ordersRefund);
    }

}
